# -*- coding: utf-8 -*-

"""
 (c) 2023 - Copyright CTyunOS Inc

 Authors:
   youyifeng <youyf2@chinatelecom.cn>

"""
import json
import logging

import requests
from cve_ease.helper import notifier_timestamp
from cve_ease.models import CVE, SA

from .base import NotifierBase

logger = logging.getLogger('cve-ease')


class Feishu(NotifierBase):
    """
    飞书群聊机器人
    官方文档: https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN?lang=zh-CN
    每个机器人每分钟最多发送20条消息到群里，如果超过20条，会限流10分钟。
    请妥善保存好此 webhook 地址，不要公布在Gitlab、博客等可公开查阅的网站上，避免地址泄露后被恶意调用发送垃圾消息
    """

    def __init__(self, gconfig):
        self._name = 'feishu_notifier'
        self._config = gconfig
        # setting wanip
        if not hasattr(gconfig, 'WANIP'):
            from cve_ease.helper import get_wanip
            self._wanip = get_wanip()
        else:
            self._wanip = gconfig.WANIP
        # setting watcher
        self._watcher = None
        if hasattr(gconfig, 'WATCHER'):
            self._watcher = gconfig.WATCHER
        # setting notify timestamp
        self._notify_time = notifier_timestamp()

    def do_send(self, data, key: str):
        res = None
        headers = {'Content-Type': 'application/json; charset=utf-8'}
        url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{key}'
        r = requests.post(url=url, headers=headers, data=json.dumps(data))
        try:
            res = json.loads(r.text)
        except:
            pass
        if r.status_code == 200 and res and 'StatusCode' in res and 0 == res['StatusCode']:
            logger.debug(f'[+] {self._name} {key} send file success!')
        else:
            logger.error("url:\n" + url)
            logger.error("request:\n" + json.dumps(data, indent=4))
            logger.error("response:\n" + r.text)
            logger.error(f'[-] {self._name} {key} send file failed!')

    def send_text(self, msg="", key=None, debug=False):
        data = {
            "msg_type": "text",
            "content": {
                "text": msg,
            },
        }
        self.do_send(data, key)

    def get_info_column(self, info_type: str):
        """
        获取信息字段，用于导出csv
        :param info_type:
        :return:
        """
        if "cve" == info_type:
            return CVE.COLUMN
        elif "sa" == info_type:
            return SA.COLUMN
        else:
            raise f" no column for {info_type} "

    def do_update_notify(self, info_diff: dict, info_type: str):
        """
        更新播报
        :param info_diff:
        :param info_type:
        :return:
        """
        for w in self._watcher:
            cw = self._watcher[w]
            if self._name not in cw:
                continue
            if len(cw[self._name]) < 1 and '' == cw[self._name][0]:
                continue

            match = {}
            notifier_token = cw[self._name][0]
            flag = False

            filter_pkglist = cw['watch_pkg'] if 'watch_pkg' in cw else []
            if 'ALL' in filter_pkglist:
                match = info_diff
                flag = True
            filter_score = cw['watch_score'] if 'watch_score' in cw else 0.0
            if filter_score <= 0.0:
                match = info_diff
                flag = True

            if not match:
                for status in info_diff:
                    if status not in match:
                        match[status] = []
                    for index, cve in enumerate(filter_diff[status]):
                        if "packageName" in cve and "" != cve["packageName"]:
                            for fpkg in filter_pkglist:
                                if fpkg in filter_pkglist:
                                    flag = True
                                    match[status].append(cve)
                        elif "cvsssCoreNVD" in cve \
                                and "" != cve["cvsssCoreNVD"] \
                                and float(cve["cvsssCoreNVD"]) >= filter_score:
                            flag = True
                            match[status].append(cve)
                        elif "cvsssCoreOE" in cve \
                                and "" != cve["cvsssCoreOE"] \
                                and float(cve["cvsssCoreOE"]) >= filter_score:
                            flag = True
                            match[status].append(cve)
            if not flag:
                logger.debug(" no update need notify")
                return

            self.send_text(self.pretty_update_info(info_type, match), key=notifier_token)

        logger.debug(f" * {self._name} do_update_notify done!")

    def do_status_notify(self, info_list: list, info_type: str):
        """
        状态播报
        :param info_list:
        :param info_type:
        :return:
        """
        for w in self._watcher:
            cw = self._watcher[w]
            if self._name not in cw:
                continue
            if len(cw[self._name]) < 2 and '' == cw[self._name][1]:
                continue

            notifier_token = cw[self._name][1]
            match = []
            flag = False

            filter_pkglist = cw['watch_pkg'] if 'watch_pkg' in cw else []
            if 'ALL' in filter_pkglist:
                match = info_list
                flag = True

            filter_score = cw['watch_score'] if 'watch_score' in cw else 0.0
            if filter_score <= 0.0:
                match = info_list
                flag = True

            if not match:
                for index, cve in enumerate(info_list):
                    if "packageName" in cve and "" != cve["packageName"]:
                        for fpkg in filter_pkglist:
                            if fpkg in filter_pkglist:
                                flag = True
                                match.append(cve)
                    elif "cvsssCoreNVD" in cve \
                            and "" != cve["cvsssCoreNVD"] \
                            and float(cve["cvsssCoreNVD"]) >= filter_score:
                        flag = True
                        match.append(cve)
                    elif "cvsssCoreOE" in cve \
                            and "" != cve["cvsssCoreOE"] \
                            and float(cve["cvsssCoreOE"]) >= filter_score:
                        flag = True
                        match.append(cve)

            logger.debug(
                f" {self._name} do_status_nofity {w} info:{info_type} filter:{filter_pkglist} len:{len(match)}"
            )

            if flag and self._name in cw and 0 != len(match):
                info = self.pretty_status_info(info_type, match)
                self.send_text(msg=info, key=notifier_token)
        logger.debug(f" * {self._name} do_status_notify done!")

    def pretty_status_info(self, info_type: str, list_info: list):
        """
        格式化状态播报信息，多态
        :param info_type:
        :param list_info:
        :return:
        """
        pretty_func_name = f"do_{info_type}_status_pretty"
        if super().hasFunction(pretty_func_name):
            return getattr(self, pretty_func_name)(list_info)
        else:
            logger.debug(f" no func {pretty_func_name} found in {self.__class__.__name__}")
            return ""

    def pretty_update_info(self, info_type: str, dict_info: dict):
        """
        格式化更新播报信息，多态
        :param info_type:
        :param dict_info:
        :return:
        """
        pretty_func_name = f"do_{info_type}_update_pretty"
        if super().hasFunction(pretty_func_name):
            return getattr(self, pretty_func_name)(dict_info)
        else:
            logger.debug(f" no func {pretty_func_name} found in {self.__class__.__name__}")
            return ""

    def per_cve_info_pretty(self, record):
        """
        每CVE信息格式化
        :param record:
        :return:
        """
        return (
                "评分:[{}] {}\n" +
                " 包名: {}\n" +
                " 发布时间: {}\n" +
                " https://www.openeuler.org/zh/security/cve/detail/?cveId={}&packageName={}\n"
        ).format(
            record["cvsssCoreNVD"] if record["cvsssCoreNVD"] else record["cvsssCoreOE"],
            record["cveId"],
            record["packageName"],
            record["updateTime"],
            record["cveId"],
            record["packageName"],
        )

    def do_cve_status_pretty(self, list_info: list):
        """
        CVE信息 状态播报 格式化
        :param list_info:
        :return:
        """
        message = (

                "Msg from CVE-EASE: \n" +
                " CVE安全公告 - 状态播报\n" +
                " {}\n" +
                "服务状态: OK\n" +
                "服务IP: {}\n" +
                "CVE记录总数: {}\n" +
                "Top{}变更信息概况:\n"
        ).format(
            self._notify_time,
            self._wanip,
            len(list_info),
            self._config.NOTIFIER_RECORD_NUM
        )
        count = int(self._config.NOTIFIER_RECORD_NUM)
        for r in list_info[:count]:
            message += self.per_cve_info_pretty(r)

        message += """\n相关同事请务必及时修复漏洞，杜绝隐患保障系统安全。\n"""
        return message

    def do_cve_update_pretty(self, dict_info: dict):
        """
        CVE信息 更新播报 格式化
        :param dict_info:
        :return:
        """
        message = (
                "Msg from CVE-EASE: \n" +
                " CVE安全公告 - 更新播报\n" +
                " {}\n" +
                "服务状态: OK\n" +
                "服务IP: {}\n" +
                "CVE记录总数: {}\n" +
                "更新情况:\n" +
                " 新增:{}" +
                " 修改:{}" +
                " 删减:{}\n" +
                "Top{}变更信息概况:\n"
        ).format(
            self._notify_time,
            self._wanip,
            len(dict_info["data"]),
            len(dict_info["add"]),
            len(dict_info["modify"]),
            len(dict_info["delete"]),
            self._config.NOTIFIER_RECORD_NUM
        )
        count = int(self._config.NOTIFIER_RECORD_NUM)
        addlist = sorted(dict_info["add"], key=lambda d: d['id'])
        modifylist = sorted(dict_info["modify"], key=lambda d: d['id'])
        dellist = sorted(dict_info["delete"], key=lambda d: d['id'])

        need_output_record_list = addlist + modifylist + dellist
        for r in need_output_record_list[:count]:
            message += self.per_cve_info_pretty(r)

        message += """\n相关同事请务必及时修复漏洞，杜绝隐患保障系统安全。\n"""
        return message

    def per_sa_info_pretty(self, record):
        """
        每SA信息 格式化
        :param record:
        :return:
        """
        return (
                "编号:{}\n" +
                " 包名: {}\n" +
                " CVEID: {}\n" +
                " 发布时间: {}\n" +
                " https://www.openeuler.org/zh/security/safety-bulletin/detail/?id={}\n"
        ).format(
            record["securityNoticeNo"],
            record["affectedComponent"],
            record["cveId"],
            record["updateTime"],
            record["securityNoticeNo"],
        )

    def do_sa_status_pretty(self, list_info: list):
        """
        SA信息 状态播报格式化
        :param list_info:
        :return:
        """
        message = (
                "Msg from CVE-EASE: \n" +
                " SA安全公告 - 状态播报\n" +
                " {}\n" +
                "服务状态: OK\n" +
                "服务IP: {}\n" +
                "SA记录总数: {}\n" +
                "更新情况:\n" +
                "Top {} 信息概况:\n" +
                "\n"
        ).format(
            self._notify_time,
            self._wanip,
            len(list_info),
            self._config.NOTIFIER_RECORD_NUM
        )
        count = int(self._config.NOTIFIER_RECORD_NUM)
        for r in list_info[:count]:
            message += self.per_sa_info_pretty(r)

        message += """\n相关同事请务必及时修复漏洞，杜绝隐患保障系统安全。\n"""
        return message

    def do_sa_update_pretty(self, dict_info: dict):
        """
        SA信息 更新播报 格式化
        :param dict_info:
        :return:
        """
        message = (
                "Msg from CVE-EASE: \n" +
                " SA安全公告 - 更新播报\n" +
                " {}\n" +
                "服务状态: OK\n" +
                "服务IP: {}\n" +
                "SA记录总数: {}\n" +
                "更新情况:\n" +
                " 新增:{}" +
                " 修改:{}" +
                " 删减:{}\n" +
                "Top{}变更信息概况:\n"
        ).format(
            self._notify_time,
            self._wanip,
            len(dict_info["data"]),
            len(dict_info["add"]),
            len(dict_info["modify"]),
            len(dict_info["delete"]),
            self._config.NOTIFIER_RECORD_NUM
        )
        count = int(self._config.NOTIFIER_RECORD_NUM)
        addlist = sorted(dict_info["add"], key=lambda d: d['id'])
        modifylist = sorted(dict_info["modify"], key=lambda d: d['id'])
        dellist = sorted(dict_info["delete"], key=lambda d: d['id'])

        need_output_record_list = addlist + modifylist + dellist
        for r in need_output_record_list[:count]:
            message += self.per_sa_info_pretty(r)

        message += """\n相关同事请务必及时修复漏洞，杜绝隐患保障系统安全。\n"""
        return message
